package it.tukano.app;

import it.tukano.app.AppModule;
import it.tukano.fun.Function1;
import it.tukano.log.Log;
import it.tukano.reflection.MethodExt;

/**
 * Base class for the modules
 * @author pgi
 */
public abstract class AbstractAppModule implements AppModule {

    /**
     * The application of this module. Initialized in initModule
     */
    private AppModule context;

    /**
     * Initializes this module. Set the context field and jumps to postInitialize. Extensions should define the postInitialize method.
     * @param context the context of the application
     */
    @Override
    public void initModule(AppModule context) {
        setContext(context);
        postInitialize();
    }

    /**
     * Starts this module. Empty. Extensions should override this method if interested in doing something at application's start
     * @param context the application
     */
    @Override
    public void startModule(AppModule context) {
    }

    /**
     * Stops this module. Empty. Extensions should override this if interested int doing something at application's stop
     * @param context the application
     */
    @Override
    public void stopModule(AppModule context) {
    }

    /**
     * Destroyes this module. Empty. Extensions should override this to perform final cleanup
     * @param context
     */
    @Override
    public void destroyModule(AppModule context) {
    }

    /**
     * Sets the value of the context field. Used by initializeModule
     * @param context the value for the context.
     */
    protected void setContext(AppModule context) {
        this.context = context;
    }

    /**
     * Returns the value of the context field. The value exists after the initializeModule call (ie from postInit to destroyModule, inclusive)
     * @return the context of this module
     */
    protected AppModule getContext() {
        return context;
    }

    /**
     * Calls the find method of the context registered during initialization. This can be called after the invocation (by the framework) of the initializeModule method
     * (ie from postInit to destroyModule)
     * @param <A> the type of the module to get
     * @param moduleClass the module's class.
     * @return the first module of type B <: A found in the context of this module or null if no such module exists
     */
    @Override
    public <A extends AppModule> A find(Class<A> moduleClass) {
        return getContext().<A>find(moduleClass);
    }
    
    /**
     * Executes a function if a module of type moduleClass exists
     * @param <A> the type of the module
     * @param moduleClass the class of the module
     * @param callback the function to call with the module, if any
     */
    public <A extends AppModule> void withModule(Class<A> moduleClass, Function1<A, ?> callback) {
        getContext().<A>withModule(moduleClass, callback);
    }
    
    @SuppressWarnings("unchecked")
    protected void invokeWithModules(String methodName, Class<?>... types) {
        MethodExt ext = new MethodExt(this, methodName, types);
        if(types != null && types.length != 0) {
            Object[] modules = new Object[types.length];
            for (int i = 0; i < types.length; i++) {
                Class<?> r = types[i];
                if(AbstractAppModule.class.isAssignableFrom(r)) {
                    Class<? extends AbstractAppModule> c = (Class<? extends AbstractAppModule>) types[i];
                    AbstractAppModule module = find(c);
                    if(module != null) {
                        modules[i] = module;
                    } else {
                        Log.message("No module found for class: ", c);
                        return;
                    }
                } else {
                    Log.message("Class ", r, " is not an AbstractAppModule subtype");
                }
            }
            ext.invoke(modules);
        } else {
            ext.invoke();
        }
    }
    
    /**
     * Called by the framework after the initialization of the module, before the start method. Subclasses should implement this to perform initialization of
     * the module. The context of this module has been set (getContext() returns a value != null)
     */
    protected void postInitialize() {
    }
}
